#include <iostream>
#include <sstream>
#include "../include/luaManager.hpp"

LuaManager* LuaManager::m_pInstance = NULL;

LuaManager* LuaManager::Instance() {
  if (m_pInstance == NULL) // only allow one instance
    m_pInstance = new LuaManager;
	
  return m_pInstance;
}

void LuaManager::KillInstance() {
	delete m_pInstance;
	m_pInstance = NULL;
}

LuaManager::LuaManager() {
  lua_state = lua_open();
  luaL_openlibs(lua_state);
  luabind::open(lua_state);
}

LuaManager::~LuaManager() {
  lua_close(lua_state);
  lua_state = NULL;
}

LuaScript::LuaScript() {
  filename = "";
  lua_stack = NULL;
}

LuaScript::~LuaScript() {
  filename = "";
  luaTables.clear();
  // TODO iterate through open files and make sure they get closed?
}

// Lua Files
bool LuaScript::isFileOpen(std::string _filename) {
  if (std::find(openFiles.begin(),openFiles.end(),_filename) != openFiles.end()) {
    return true;
  }
  return false;
}

bool LuaScript::openFile(std::string _filename) {
  if (isFileOpen(_filename)) {
    std::cerr << "Error: Attempted to open already open file " << _filename << std::endl;
	return false;
  }
  filename = _filename;
  // TODO check if filename exists:
  lua_checkstack(LuaManager::m_pInstance->getLuaState(), 1);
  lua_stack = lua_newthread(LuaManager::m_pInstance->getLuaState());
  // Load and execute script file
  if ((luaL_loadfile(lua_stack, filename.c_str()) != 0) || lua_pcall(lua_stack, 0, 0, 0)) {
    std::cerr << "Error: could not open script file " << filename << std::endl;
    puts(lua_tostring(lua_stack, -1));
	return false;
  }
  LuaManager::m_pInstance->addOpenFile(this);
  return true;
}

void LuaManager::addOpenFile(LuaScript *script) {
	openFiles.insert(std::make_pair(script->filename, script));
	// add to open threads if not yet open
	if (openThreads.find(script->filename) == openThreads.end())
		openThreads[script->filename] = script->lua_stack;
}

void LuaManager::removeOpenFile(LuaScript *script) {
	openFiles.erase(script->filename);
}

bool LuaScript::closeFile() {
  if (lua_stack == NULL) {
    std::cerr << "Error closing: was the lua state open?\n";
	return false;
  }
  lua_stack = NULL;
  luaTables.clear();
  LuaManager::m_pInstance->removeOpenFile(this);
  return true;
}

// Lua Tables
std::string LuaScript::openTablespace() {
  if (filename != "") {
    std::cerr << "Error: no lua file is open\n";
	return std::string();
  } 
  // Extract base file from filename
  int dot = filename.find(".");
  int last_slash = filename.find_last_of("/");
  std::string tablespace = filename.substr(last_slash+1, dot - (last_slash+1));
  if (tablespace.empty()) {
    std::cerr << "Error: extracting basename for file: " << filename << std::endl;
	return tablespace;
  }
  openTable(tablespace);
  return tablespace;
}

void LuaScript::openTable(const std::string& table_name) {
  if (luaTables.size() == 0) {
    lua_getglobal(lua_stack, table_name.c_str());
	if (!lua_istable(lua_stack, -1)) {
	  std::cerr << "Error: This is not a lua table\n";
	  return;
	}
	luaTables.push_back(table_name);
  } else {
    lua_pushstring(lua_stack, table_name.c_str());
	lua_gettable(lua_stack, -2);
	if (!lua_istable(lua_stack, -1)) {
	  std::cerr << "Error: this is not a lua table\n";
	  return;
	}
	luaTables.push_back(table_name);
  }
}

void LuaScript::openTable(int table_name) {
  if (luaTables.size() == 0) {
	  std::cerr << "Warning: there are no open tables\n";
  }
  lua_pushnumber(lua_stack, table_name);
  if (!lua_istable(lua_stack, -2)) {
	  std::cerr << "Warning: stack location is not a table\n";
  }
  lua_gettable(lua_stack, -2);
  if (!lua_istable(lua_stack, -1)) {
	  std::cerr << "Error: this is not a lua table\n";
	  return;
  }
  std::stringstream ss;
  ss << table_name;
  luaTables.push_back(ss.str());
}

void LuaScript::closeTable() {
  if (luaTables.size() == 0) {
    std::cerr << "Error: tried to close a table when none should exist!\n";
  }
  luaTables.pop_back();
  lua_pop(lua_stack, 1);
}

void LuaScript::closeAllTables() {
  while (luaTables.size() != 0) {
    closeTable();
  }
}

// Lua Functions
luabind::object LuaScript::ReadFunctionPointer(const std::string& key) {
  if (luaTables.size() == 0) {
    lua_getglobal(lua_stack, key.c_str());
	luabind::object o(luabind::from_stack(lua_stack, -1));
	if (!o) {
	  std::cerr << "Error: failed to load lua function " << key << "\n";
	  return luabind::object();
	}
	if (luabind::type(o) != LUA_TFUNCTION) {
	  std::cerr << "Error: key " << key << " does not represent a function in the script.\n";
	  return luabind::object();
	}
	return o;
  } else {
    luabind::object o(luabind::from_stack(lua_stack, -1));
	if (luabind::type(o) != LUA_TTABLE) {
	  std::cerr << "Error: failed to load the tables from the top of the stack.\n";
	  return luabind::object();
	}
	if (luabind::type(o[key]) != LUA_TFUNCTION) {
	  std::cerr << "Error: key " << key << " does not represent a function in the script.\n";
	  return luabind::object();
	}
	return o[key];
  }
}

luabind::object LuaScript::ReadFunctionPointer(int key) {
	luabind::object o(luabind::from_stack(lua_stack, -1));
	if (luabind::type(o) != LUA_TTABLE) {
		std::cerr << "Warning: top of the stack was not a table.\n";
		return o;
	}
	if (luabind::type(o[key]) != LUA_TFUNCTION) {
		std::cerr << "Warning: data retrieved was not a function.\n";
		return o;
	}
	return o[key];
}

bool LuaScript::doesDataExist(int key, int type) {
	if (luaTables.size() == 0) {
		std::cerr << "Error: there are no open tables.\n";
		return false;
	}
	luabind::object o (luabind::from_stack(lua_stack, -1));
	if (luabind::type(o) != LUA_TTABLE) {
		std::cerr << "Error: top of the stack is not a table.\n";
		return false;
	}
	luabind::object obj(o[key]);
	return (type == luabind::type(o));
}

bool LuaScript::doesDataExist(std::string key, int type) {
	if (luaTables.size() == 0) {
		// Global variable
		lua_getglobal(lua_stack, key.c_str());
		luabind::object o(luabind::from_stack(lua_stack, -1));
		return (type == luabind::type(o));
	} else {
		luabind::object o(luabind::from_stack(lua_stack, -1));
		if (luabind::type(o) != LUA_TTABLE) {
			std::cerr << "Error: top of the stack is not a table.\n";
			return false;
		}
		luabind::object obj(o[key]);
		return (type == luabind::type(o));
	}
}
